from typing import Dict, Optional, Tuple
import numpy as np
from qiskit import QuantumCircuit
from hardware import HardwareParams
from quantum_chip import QuantumChip
import pandas as pd
import networkx as nx
from copy import deepcopy
import math
from qrmove_ds import QRMoveMatrix, QRMoveDAG


class QRMoveCompiler:
    def __init__(
        self,
        quantum_circuit: QuantumCircuit,
        quantum_chip: QuantumChip,
        hardware_params: HardwareParams = None,
        extra_qubit_num: int = 0,
    ):
        """
        quantum_circuit: 待优化的量子电路，类型为 QuantumCircuit
        quantum_chip: 量子芯片，包括芯片形状，芯片大小
        hardware_params: 硬件参数
        """
        self.quantum_circuit: QuantumCircuit = quantum_circuit
        self.quantum_chip: QuantumChip = quantum_chip
        self.hardware_params: HardwareParams = hardware_params
        self.extra_qubit_num = extra_qubit_num
        self.circuit_matrix: QRMoveMatrix = QRMoveMatrix(
            quantum_circuit, quantum_chip, hardware_params
        )

    def circuit_depth(self):
        return self.circuit_matrix.get_circuit_depth()

    def compile_program(self):
        # 编译程序，三阶段优化
        start_depth = self.circuit_depth()
        start_width = self.quantum_circuit.width()
        # Stage 1：拆分并组合电路、拉取以最小化电路宽度
        self.pull_to_min_width()
        # self.circuit_matrix.visual_dag()
        qr_map_depth = self.circuit_depth()
        qr_map_width = len(self.circuit_matrix.circuit_dag.get_no_none_col())
        # Stage 2：消除气泡
        self.eliminate_idle_period()
        for _ in range(self.extra_qubit_num):
            # Stage 3：通过额外的量子比特，来进行深度压缩
            self.compress_depth_with_extra_qubit()
        qr_move_depth = self.circuit_depth()
        qr_move_width = len(self.circuit_matrix.circuit_dag.get_no_none_col())
        return (
            start_depth,
            start_width,
            qr_map_depth,
            qr_map_width,
            qr_move_depth,
            qr_move_width,
        )

    def compress_depth_with_extra_qubit(self):
        # Stage 3：通过额外的量子比特，来进行深度压缩
        _dag: QRMoveDAG = self.circuit_matrix.circuit_dag  # DAG图
        _dag.compress_depth_with_extra_qubit()

    def eliminate_idle_period(self):
        # Stage 2：消除气泡
        _dag: QRMoveDAG = self.circuit_matrix.circuit_dag  # DAG图
        _dag.compress_depth_with_existing_qubit()  # 通过已有的量子比特，对深度进行压缩

    def pull_to_min_width(self):
        # Stage 1：拆分并组合电路、拉取以最小化电路宽度，将电路的宽度拉到极致

        matrix = self.circuit_matrix
        pivot_idx = matrix.get_pivot_idx()  # 枢轴

        history_pivot_idx = [pivot_idx]

        def near_col(col_idx):
            # 获取 col_idx 列的相邻列
            col_num = len(matrix.circuit_dag.matrix_column)
            near_col_idx = []
            for i in range(1, col_num):
                if col_idx - i >= 0:
                    near_col_idx.append(col_idx - i)
                if col_idx + i < col_num:
                    near_col_idx.append(col_idx + i)
            return near_col_idx

        while True:
            col_num = len(matrix.circuit_dag.matrix_column)
            row_num, col_num = matrix.matrix.shape
            pulled_logic_qid = []

            # 先拉取枢轴上自带 gate_id 的相应的另一列
            for block in matrix.circuit_dag.get_blocks_by_column_id(pivot_idx):
                for node in block.nodes:
                    other_block = (
                        node.belong_block_b
                        if node.belong_block_a.column_id == pivot_idx
                        else node.belong_block_a
                    )
                    if other_block == None:
                        # 单比特门，直接跳过
                        continue
                    matrix.try_pull_block(
                        other_block.column_id,
                        other_block.logic_qid,
                        pivot_idx,
                        other_block,
                    )
                    pulled_logic_qid.append(other_block.logic_qid)

            # 再拉取不带相同 gate_id 的列
            for j in near_col(pivot_idx):
                blocks = matrix.circuit_dag.get_blocks_by_column_id(j)
                for block in blocks:
                    if block.logic_qid not in pulled_logic_qid:
                        if block.column_id == None:
                            print(123)
                        matrix.try_pull_block(
                            block.column_id, block.logic_qid, pivot_idx, block
                        )

            # 重新计算枢轴
            tmp_pivot = matrix.get_pivot_idx()
            history_pivot_idx.append(tmp_pivot)

            # print("tmp_pivot:", tmp_pivot)

            if tmp_pivot == pivot_idx:
                break

            def has_period_cycle(
                history_pivot_idx, min_cycle_length=2, max_cycle_length=5
            ):
                """检测是否存在周期性循环"""
                history_len = len(history_pivot_idx)
                # 检查不同长度的周期
                for cycle_length in range(
                    min_cycle_length, min(max_cycle_length + 1, history_len // 2 + 1)
                ):
                    # 如果历史记录足够长，检查是否存在重复模式
                    if history_len >= 2 * cycle_length:
                        recent_segment = history_pivot_idx[-cycle_length:]
                        previous_segment = history_pivot_idx[
                            -2 * cycle_length : -cycle_length
                        ]
                        # 如果最近的序列与之前的序列相同，则检测到周期
                        if recent_segment == previous_segment:
                            return True, cycle_length
                return False, 0

            # 在循环中使用 检测周期性
            is_cycle, cycle_length = has_period_cycle(history_pivot_idx)
            if is_cycle:
                # print(f"周期性振荡，周期长度为: {cycle_length}")
                break

        # matrix.restore_matrix()
        # self.circuit_matrix.circuit_dag.print_block_depth()
        # matrix.visual_dag()
